#include "DeviceData.h"
#include "SystemAllocation.h"
#include <fstream>
#include <iostream>


DeviceData::DeviceData(string _kind, int _capacity, int _cost, 
						double _overHead, int _readRate, int _writeRate, int _energy) 
{
	kind = _kind;
	capacity = _capacity;
	cost = _cost;
	overhead = _overHead;
	readRate = _readRate; 
	writeRate = _writeRate;
	energy = _energy;
	maxUtil = 1;
	C = 0;
	size = 0;
	deviceCount = 0;

	clearUtilData();
}

DeviceData::~DeviceData(void)
{
}

double DeviceData::setSize(double devCount){
	deviceCount = devCount;
	double sizeOfoneDevice = (capacity * (GIGA)); 
	size = deviceCount * sizeOfoneDevice;
	cout << "Number of " << kind << " devices is " << deviceCount << " Total Size is " << size << "MB" << endl;
	return size; 
}

string DeviceData::getKind()
{
	return kind;
}

int DeviceData::getCapacity()
{
	return capacity;
}

int DeviceData::getCost()
{
	return cost;
}

double DeviceData::getOverhead()
{
	return overhead;
}

double DeviceData::getReadRate()
{
	return readRate;
}

double DeviceData::getWriteRate()
{
	return writeRate;
}

void DeviceData::setMaxUtil(double max)
{
	maxUtil = max;
}

double DeviceData::getMaxUtil()
{
	return maxUtil;
}

int DeviceData::getDeviceCount()
{
	return deviceCount;
}

void DeviceData::setDeviceCount(int num)
{
	deviceCount = num;
}

void DeviceData::subReadWrite(int time ,ExtentStatsAlgo* e){
	ExtentStatistics* temp;
	temp=e->getStat(time);
	if (temp!=0){
		TotalReadWrite.extentsNum--;
		TotalReadWrite.kr = TotalReadWrite.kr - temp->getStat(kilo_rm); 
		TotalReadWrite.kw = TotalReadWrite.kw -  temp->getStat(kilo_w);
		TotalReadWrite.missSum = TotalReadWrite.missSum - temp->getStat(rand_rm);
		TotalReadWrite.missSeq = TotalReadWrite.missSeq -  temp->getStat(seq_r);
		
		int prioGroup = e->getExtent()->getLun()->getPriorityGroup();
		switch (prioGroup){
			case 1:
				TotalReadWriteU1.extentsNum--;
				TotalReadWriteU1.kr = TotalReadWriteU1.kr - temp->getStat(kilo_rm); 
				TotalReadWriteU1.kw = TotalReadWriteU1.kw -  temp->getStat(kilo_w);
				TotalReadWriteU1.missSum = TotalReadWriteU1.missSum - temp->getStat(rand_rm);
				TotalReadWriteU1.missSeq = TotalReadWriteU1.missSeq -  temp->getStat(seq_r);
				break;
			case 2:
				TotalReadWriteU2.extentsNum--;
				TotalReadWriteU2.kr = TotalReadWriteU2.kr - temp->getStat(kilo_rm); 
				TotalReadWriteU2.kw = TotalReadWriteU2.kw -  temp->getStat(kilo_w);
				TotalReadWriteU2.missSum = TotalReadWriteU2.missSum - temp->getStat(rand_rm);
				TotalReadWriteU2.missSeq = TotalReadWriteU2.missSeq -  temp->getStat(seq_r);
				break;
			case 3:
				TotalReadWriteU3.extentsNum--;
				TotalReadWriteU3.kr = TotalReadWriteU3.kr - temp->getStat(kilo_rm); 
				TotalReadWriteU3.kw = TotalReadWriteU3.kw -  temp->getStat(kilo_w);
				TotalReadWriteU3.missSum = TotalReadWriteU3.missSum - temp->getStat(rand_rm);
				TotalReadWriteU3.missSeq = TotalReadWriteU3.missSeq -  temp->getStat(seq_r);
				break;
			default:
				cout<< "prio not in range [1..3] !!!!  "<< prioGroup<< endl;
		}
	}
}

void DeviceData::addReadWrite(int time ,ExtentStatsAlgo* e){
	ExtentStatistics* temp;
	temp=e->getStat(time);
	if (temp!=0){
		TotalReadWrite.extentsNum++;
		TotalReadWrite.kr += temp->getStat(kilo_rm);
		TotalReadWrite.kw += temp->getStat(kilo_w);
		TotalReadWrite.missSum += temp->getStat(rand_rm);
		TotalReadWrite.missSeq += temp->getStat(seq_r);

		int prioGroup = e->getExtent()->getLun()->getPriorityGroup();
		switch (prioGroup){
			case 1:
				TotalReadWriteU1.extentsNum++;
				TotalReadWriteU1.kr += temp->getStat(kilo_rm);
				TotalReadWriteU1.kw += temp->getStat(kilo_w);
				TotalReadWriteU1.missSum += temp->getStat(rand_rm);
				TotalReadWriteU1.missSeq += temp->getStat(seq_r);
				break;
			case 2:
				TotalReadWriteU2.extentsNum++;
				TotalReadWriteU2.kr += temp->getStat(kilo_rm);
				TotalReadWriteU2.kw += temp->getStat(kilo_w);
				TotalReadWriteU2.missSum += temp->getStat(rand_rm);
				TotalReadWriteU2.missSeq += temp->getStat(seq_r);
				break;
			case 3:
				TotalReadWriteU3.extentsNum++;
				TotalReadWriteU3.kr += temp->getStat(kilo_rm);
				TotalReadWriteU3.kw += temp->getStat(kilo_w);
				TotalReadWriteU3.missSum += temp->getStat(rand_rm);
				TotalReadWriteU3.missSeq += temp->getStat(seq_r);
				break;
			default:
				cout<< "prio not in range [1..3] !!!!  " << prioGroup<< endl;
		}
	}
}

void DeviceData::clearUtilData(){
	TotalReadWrite.extentsNum = 0;
	TotalReadWrite.kr = 0 ; 
	TotalReadWrite.kw = 0 ; 
	TotalReadWrite.missSum = 0 ; 
	TotalReadWrite.missSeq = 0 ; 

	TotalReadWriteU1.kr = 0;
	TotalReadWriteU1.kw = 0;
	TotalReadWriteU1.missSum = 0;
	TotalReadWriteU1.missSeq = 0;
	TotalReadWriteU1.extentsNum = 0;

	TotalReadWriteU2.kr = 0;
	TotalReadWriteU2.kw = 0;
	TotalReadWriteU2.missSum = 0;
	TotalReadWriteU2.missSeq = 0;
	TotalReadWriteU2.extentsNum = 0;

	TotalReadWriteU3.kr = 0;
	TotalReadWriteU3.kw = 0;
	TotalReadWriteU3.missSum = 0;
	TotalReadWriteU3.missSeq = 0;
	TotalReadWriteU3.extentsNum = 0;
}

//Equation 2 from the article
// calc util for device for all devices in the same kind in the system
// in <time> slice , need to enter the system view times (found in SystemView)
double DeviceData::calcUtilization(TotalStat total)
{
	double dt ,t;
	if (total.extentsNum == 0 || readRate == 0 || writeRate == 0  || deviceCount == 0 ){
		return 0;
	}
	dt = 3600 * deviceCount; // assumes that time slice size is final
	t = ((total.kr/1024) / readRate)+ ((total.kw/1024) / writeRate) + (overhead * total.missSum)  ;//+ (C * total.missSeq);

	return t/dt;
}

double DeviceData::calcUtilGradientDescent(int i){
	double dt ,t;
	TotalStat total;
	if (i==0)
		total=TotalReadWrite;
	else if (i==1)
		total=TotalReadWriteU1;
	else if (i==2)
		total=TotalReadWriteU2;
	else if (i==3)
		total=TotalReadWriteU3;

	if (total.extentsNum == 0 || readRate == 0 || writeRate == 0  || deviceCount == 0 ){
		return 0;
	}
	dt = 3600 * deviceCount; // assumes that time slice size is final
	t = ((total.kr/1024) / readRate)+ ((total.kw/1024) / writeRate) + (overhead * total.missSum)  ;//+ (C * total.missSeq);

	return t/dt;
}

// take entry and clac the utilization with the entry 
// return if the result is below max util 
// not change the state (do roll back to data)
bool DeviceData::checkUtilization(int time ,ExtentStatsAlgo* entry)
{
	addReadWrite(time ,entry);
	double util = calcUtilization(TotalReadWrite);
	subReadWrite(time ,entry);
	return (util <= maxUtil);
	//return true;
}

bool DeviceData::checkUtilization(int time ,list<ExtentStatsAlgo*> entries){
	list<ExtentStatsAlgo*>::iterator entriesIt;
	for(entriesIt = entries.begin(); entriesIt != entries.end(); entriesIt++){
		addReadWrite(time ,*entriesIt);
	}
	double util = calcUtilization(TotalReadWrite);
	for(entriesIt = entries.begin(); entriesIt != entries.end(); entriesIt++){
		subReadWrite(time ,*entriesIt);
	}
	return (util <= maxUtil);
	//return true;
}

// update util with the new added entry and save the result
void DeviceData::encUtilization(int time , ExtentStatsAlgo* entry)
{
	addReadWrite(time,entry);
	double util = calcUtilization(TotalReadWrite);
	double utilU1 = calcUtilization(TotalReadWriteU1);
	double utilU2 = calcUtilization(TotalReadWriteU2);
	double utilU3 = calcUtilization(TotalReadWriteU3);
	saveUtil(time, util, utilU1, utilU2, utilU3);
}

// update util with the new added entry without saving
void DeviceData::encUtilizationNoSave(int time , ExtentStatsAlgo* entry)
{
	addReadWrite(time,entry);
}

// update util with the removed entry and save the result
void DeviceData::decUtilization(int time , ExtentStatsAlgo* entry)
{
	subReadWrite(time,entry);
	double util = calcUtilization(TotalReadWrite);
	double utilU1 = calcUtilization(TotalReadWriteU1);
	double utilU2 = calcUtilization(TotalReadWriteU2);
	double utilU3 = calcUtilization(TotalReadWriteU3);
	saveUtil(time, util, utilU1, utilU2, utilU3);
}

void DeviceData::saveUtil(int time, double util, double utilU1, double utilU2, double utilU3)
{ 
	if(util>1)
		cout<< "XXXXXX Util bigger then 1 in "<<kind<<" device!! XXXXXX"<<endl ;
	if(utilizationRecords.find(time) != utilizationRecords.end())
		utilizationRecords[time] = util;
	else
		utilizationRecords.insert(pair<int,double>(time,util));
	
	if(utilizationRecordsU1.find(time) != utilizationRecordsU1.end())
		utilizationRecordsU1[time] = utilU1;
	else
		utilizationRecordsU1.insert(pair<int,double>(time,utilU1));
	
	if(utilizationRecordsU2.find(time) != utilizationRecordsU2.end())
		utilizationRecordsU2[time] = utilU2;
	else
		utilizationRecordsU2.insert(pair<int,double>(time,utilU2));
	
	if(utilizationRecordsU3.find(time) != utilizationRecordsU3.end())
		utilizationRecordsU3[time] = utilU3;
	else
		utilizationRecordsU3.insert(pair<int,double>(time,utilU3));
}

double DeviceData::getUtilizationRecord(int time){
	if(utilizationRecords.find(time) != utilizationRecords.end())
		return utilizationRecords[time] ;
	else{
		utilizationRecords.insert(pair<int,double>(time,0));
		return 0;
	}
}
double DeviceData::getUtilizationRecordU1(int time){
	if(utilizationRecordsU1.find(time) != utilizationRecordsU1.end())
		return utilizationRecordsU1[time] ;
	else{
		utilizationRecordsU1.insert(pair<int,double>(time,0));
		return 0;
	}
}
double DeviceData::getUtilizationRecordU2(int time){
	if(utilizationRecordsU2.find(time) != utilizationRecordsU2.end())
		return utilizationRecordsU2[time] ;
	else{
		utilizationRecordsU2.insert(pair<int,double>(time,0));
		return 0;
	}
}
double DeviceData::getUtilizationRecordU3(int time){
	if(utilizationRecordsU3.find(time) != utilizationRecordsU3.end())
		return utilizationRecordsU3[time] ;
	else{
		utilizationRecordsU3.insert(pair<int,double>(time,0));
		return 0;
	}
}

string DeviceData::toString() {
	string ans = "";
	char buffer[100];

	ans.append("Type: " + kind);
	_itoa_s(capacity, buffer,100, 10);
	ans.append(" Capacity: " + (string)buffer);
	_itoa_s(cost, buffer,100, 10);
	ans.append(" Cost: " + (string)buffer);
	_itoa_s((int)overhead, buffer,100, 10);		// ERROR: double turns into int...
	ans.append(" Overhead: " + (string)buffer);
	_itoa_s((int)readRate, buffer,100, 10);
	ans.append(" ReadRate: " + (string)buffer);
	_itoa_s((int)writeRate, buffer,100, 10);
	ans.append(" WriteRate: " + (string)buffer);
	_itoa_s(energy, buffer, 100,10);
	ans.append(" Energy: " + (string)buffer);
	_itoa_s((int)size, buffer, 100,10);
	ans.append(" Size: " + (string)buffer);
	_itoa_s((int)deviceCount, buffer, 100,10);
	ans.append(" DeviceCount: " + (string)buffer);
	_itoa_s((int)C, buffer,100, 10);
	ans.append(" C: " + (string)buffer);

	return ans;
}

void DeviceData::startOver(){
	clearUtilData();	
	utilizationRecords.clear();
	utilizationRecordsU1.clear();
	utilizationRecordsU2.clear();
	utilizationRecordsU3.clear();
}